//
//  cli.swift
//  touc
//
//  Created by z on 2020/6/26.
//  Copyright © 2020 z. All rights reserved.
//

import Foundation
class sockMapData {
    var sock = udpSocket(-1)
    var num=0;
    var id = bytes()
    var askList=[long]();
    var  dataList=[long]();
    var createTime:long=0;
    init(_ s:udpSocket, _ port:Int,_ i:bytes, _ al:[long], _ dl:[long], _ ct:long) {
        sock = s
        num = port;
        id = i;
        askList = al;
        dataList = dl;
        createTime = ct;
    }
}

class aliveSockV {
    var so = udpSocket(-1);
    var port = -1;
    
    init(_ s:udpSocket, _ p:Int) {
        so = s;
        port = p;
    }
}

class cli:base
{
    var deadSock = [Int]()
    var aliveSock = [aliveSockV]()
    var sockMap = [Int:sockMapData]()
    var staTime = getTime()
    var staGot = 0
    var staOut = 0
    var udpCo = 0
    var lastCheckTimeout:long = 0
    var newSockThisSecond = 0
    var recvTime:long = 0
    var getNumPerPack = con_getNum
    var peerMaxPos:long = 0
    var recvMap = [long : sendMapV]()
    var rRaw = 0
    var wRaw = 0
    var timeoutTime = htimeoutTime
    var peerMaxGotLen = 0
    var peerMaxSendLen = 0
    var nullSend = 0
    var staSend = 0
    var newSockLimit = maxNewSockPerSecond
    var serverTotalSendLen = serverSenLen
    var serverOneLen = serverPackLen
    var totalSend = 0
    convenience init(_ ports:[Int]) {
        self.init(cliPackLen);
        deadSock = ports;
    }
    func getId()->bytes {
        udpCo+=1;
        if (udpCo == 60000){
            udpCo = 0;}
        var w = structWrapper()
        w.writeWord(udpCo);
        return w.data;
    }
    func dealSta(_ ss:bytes) {
        var l = [long]();
        var w = structWrapper(ss)
        
        var mi = w.readQ();
        if (mi > peerPos) {
            var le = 0;
            for i in peerPos..<mi
            {
                le += (sendMap[i]?.data.count)!;
                sendMap.removeValue(forKey: i);
            }
            peerPos = mi;
            tellAddBuf(le);
        }
        
        var ma = w.readQ();
        var d = w.getLeftData();
        var co = -1;
        var b:uint8 = 0;
        var pos = 0;
        for i in mi...ma
        {
            co+=1;
            if (co == 0) {
                b = d[pos];
                pos+=1;
                if ((b & 128) != 0){
                    l.append(i);}
                b <<= 1;
            } else {
                if ((b & 128) != 0){
                    l.append(i);}
                b <<= 1;
                if (co == 7){
                    co = -1;}
            }
        }
        for  one in l {
            if (sendMap.keys.contains(one)){
                sendMap[one]!.got = true;}
        }
    }
    
    func dealRecvPack(_ channel:udpSocket) {
        var num = sockMap[channel.fileno()]!.num;
        var id = sockMap[channel.fileno()]!.id;
        var j = channel.recvfrom(udpReadLen).0
        if j.count==0{
            addToDeadSock(channel.fileno());
            return;
        }
        var w =  structWrapper(j);
        var lll = w.readByte();
        var xx = w.readArr(lll);
        if xx != id {
            addToDeadSock(channel.fileno());
            return;
        }
        
        var askList = sockMap[channel.fileno()]!.askList;
        var  dataList = sockMap[channel.fileno()]!.dataList;
        if (dataList.count > 0 && sendMap.keys.contains(dataList[0])){
            sendMap[dataList[0]]!.got = true;}
        
        lastRecvTime = getTime()
        staGot += 1;
        rRaw += j.count;
        if (j.count > maxGotLen){
            maxGotLen = j.count;}
        var tt = getTime() - sockMap[channel.fileno()]!.createTime;
        if (tt > recvTime){
            recvTime =  tt;}
        var ttt = w.readQ();
        if (ttt > peerMaxPos){
            peerMaxPos = ttt;}
        var pmg = w.readWord();
        if (pmg > peerMaxGotLen){
            peerMaxGotLen = pmg;}
        pmg = w.readWord();
        if (pmg > peerMaxSendLen){
            peerMaxSendLen = pmg;}
        var sendNum = w.readByte();
        for i in 0..<askList.count
        {
            var nn = askList[i];
            var st = bytes()
            if (i < sendNum) {
                var le = w.readWord();
                st = w.readArr(le);
            }
            if (!recvMap.keys.contains(nn) || recvMap[nn]!.got){
                continue;}
            if (i < sendNum && st.count > 0) {
                recvMap[nn]!.got = true;
                recvMap[nn]!.data = st;
                checkRecvMap();
            } else {
                recvMap[nn]!.sendTime-=1;
            }
        }
        var hasSta = w.readByte();
        if (hasSta == 1) {
            var le = w.readWord();
            var sta = w.readArr(le);
            dealSta(sta);
        }
        
        var addPackNum = w.readByte();
        for i in 0..<addPackNum{
            var nn = w.readQ();
            var ll = w.readWord();
            var st = w.readArr(ll);
            if (nn < recvPos){
                continue;}
            var v = sendMapV();
            v.got = true;
            v.data = st;
            recvMap[nn] = v;
        }
        checkRecvMap();
        sockMap.removeValue(forKey:channel.fileno())
        var v =  aliveSockV(channel, num);
        aliveSock.append(v);
    }
    func doPeriodWork() {
        if (getTime() - lastRecvTime > 120 * 1000){
            exit(0)
        }
        timeoutTime = recvTime + con_timeoutPlus;
        if (recvTime == 0 || recvTime > htimeoutTime){
            timeoutTime = htimeoutTime;}
        if (staOut == 0){
            gotRate = 1;}
        else{
            gotRate = Float(staGot) / Float (staGot + staOut);}
        maxAskTime = calAskTime();
        print (
            sockMap.count, staGot, staOut, recvTime / 10,  (rRaw / 1024), (wNet / 1024),
            maxSenLen, peerMaxGotLen, peerMaxSendLen, maxGotLen);
        temp = "\(staGot) \(staOut) \(recvTime) || \(rRaw/1024) \(wRaw/1024) \(wNet/1024) \(rNet/1024) || \(maxSenLen) \(peerMaxGotLen) \(peerMaxSendLen) \(peerMaxGotLen)\n"
        staGot = 0
        staOut = 0
        newSockThisSecond = 0
        recvTime = 0
        staSend = 0;
        rRaw = 0
        wRaw = 0
        rNet = 0
        wNet = 0;
        nullSend = 0
        totalSend = 0;
        peerMaxGotLen = 0
        peerMaxSendLen = 0
        maxGotLen = 0
        maxSenLen = 0;
    }
    func sendPacks()  {
        var passTime:Float = Float((getTime() - staTime)) /  1000.0;
        if (passTime > 1){
            passTime = 1;}
        
        var n:Int = Int (passTime * Float(newSockLimit) - Float(newSockThisSecond));
        if (n < 0){
            n = 0;}
        if (n > deadSock.count){
            n = deadSock.count;}
        n = aliveSock.count + n;
        
        var minPackNum:Float=0
        var maxSend:Float=0;
        if (gotRate == 0){
            minPackNum = 20000
            maxSend = 20000;}
        else {
            minPackNum = Float(passTime) * (Float(serverStaPerSecond) / gotRate);
            maxSend = Float(passTime) * (Float(con_maxPackRecv) / gotRate);
        }
        
        var maxSend_int:Int = Int(maxSend - Float(totalSend));
        if (n > maxSend_int){
            n = maxSend_int;}
        
        var staSendNum:Int = Int(minPackNum - Float(staSend));
        
        if (staSendNum < 0){
            staSendNum = 0;}
        
        if (staSendNum > n){
            staSendNum = n;}
        
        staSend += staSendNum;
        var askList = getAskList(n, staSendNum);
        var dataList = getDataToSend(n);
        totalSend += n;
        for i in 0..<n{
            var al = [long]()
            var dl = [long]()
            if (askList.count > i){
                al = askList[i];}
            if (dataList.count > i){
                dl.append(dataList[i]);}
            
            var hasSta = 0;
            if (staSendNum > 0) {
                staSendNum -= 1;
                hasSta = 1;
            } else {
                hasSta = 0;
                if (al.count == 0 && dl.count == 0){
                    continue;}
            }
            
            if (aliveSock.count == 0) {
                var a = deadSock[0]
                deadSock.removeFirst()
                var s = udpSocket()
                s.setblocking(false)
                newSockThisSecond+=1
                var v =  aliveSockV(s, a);
                aliveSock.append(v);
            }
            packMsg_send(al, dl, hasSta);
            if (al.count == 0 && dl.count == 0){
                nullSend+=1;}
        }
    }
    
    func work()  {
        var sel = selector()
        for so in sockMap.keys
        {
            sel.register(so, POLLIN)
        }
        rig(sel);
        
        var ret = sel.poll(Int(calWaitTime()));
        for (fd,e) in sel.result()
        {
            dealFd(fd, e)
            if (sockMap.keys.contains(fd) && e.isReadable()) {
                dealRecvPack(sockMap[fd]!.sock);
            }
        }
        reFreshSendMap();
        var tt = getTime()
        if (tt - lastCheckTimeout > con_timeoutInternal) {
            lastCheckTimeout = tt
            dealTimeout();
        }
        sendPacks();
        if (tt - staTime > 1000) {
            staTime = tt
            doPeriodWork();
        }
    }
    
    func checkRecvMap() {
        while (recvMap.keys.contains(recvPos)
            && recvMap[recvPos]!.got) {
                wrietBuf += recvMap[recvPos]!.data;
                recvMap.removeValue(forKey: recvPos)
                recvPos+=1;
        }
    }
    
    func packMsg_send(_ askList:[long], _ dataList:[long], _ hasSta:Int)
    {
        // Collections.sort(askList);
        var tt = getTime();
        var v = aliveSock[0]
        aliveSock.removeFirst()
        
        var w = structWrapper()
        w.writeArr(key);
        var id = getId();
        w.writeByte(id.count);
        w.writeArr(id);
        w.writeQ(recvPos);
        w.writeFloat(gotRate);
        if (dataList.count == 0){
            w.writeByte(0);}
        else {
            w.writeByte(1);
            var dd = dataList[0];
            w.writeQ(dd);
            w.writeWord(sendMap[dd]!.data.count);
            w.writeArr(sendMap[dd]!.data);
        }
        var le = askList.count;
        w.writeByte(hasSta);
        w.writeByte(le);
        if (le != 0) {
            w.writeQ(askList[0]);
            for i in 0..<le-1{
                w.writeDWord((Int) (askList[i + 1] - askList[0]));
            }
        }
        var vv =  sockMapData(v.so,v.port, id, askList, dataList, tt);
        var add = (serverIp,v.port)
        var aa = w.data;
        v.so.sendto(&aa, add)
        sockMap[v.so.fileno()] = vv;
        wRaw += aa.count;
        if (aa.count > maxSenLen){
            maxSenLen = aa.count;}
    }
    func getAskList(_ n:Int, _ staPackNum2:Int)->[[long]]
    {
        var staPackNum = staPackNum2
        var l = [[long]]()
        if (n == 0){
            return l;}
        var staLen = (Int) (17 + (sendPos - peerPos) / 8);
        
        var sendNum:Int = (serverTotalSendLen - 20 - getDamMaxLen() - staLen) / (serverOneLen + 2);
        if (sendNum <= 0){
            sendNum = 0;}
        
        var tempList = [long]()
        var thisLoopSendTime = 0;
        while (true) {
            thisLoopSendTime+=1;
            if (thisLoopSendTime > maxAskTime) {
                if (tempList.count != 0 && l.count < n){
                    l.append(tempList);}
                return l;
            }
            for i in recvPos..<peerMaxPos{
                if (staPackNum > 0 && tempList.count >= sendNum) {
                    staPackNum -= 1;
                    l.append(tempList);
                    tempList = [long]()
                    if (l.count == n){
                        return l;}
                }
                
                if (!recvMap.keys.contains(i)){
                    recvMap[i]=sendMapV();}
                
                if (!recvMap[i]!.got && recvMap[i]!.sendTime < thisLoopSendTime) {
                    if (tempList.contains(i)) {
                        l.append(tempList);
                        tempList = [long]()
                        if (l.count == n){
                            return l;}
                    }
                    recvMap[i]!.sendTime+=1;
                    tempList.append(i);
                    if (tempList.count == getNumPerPack) {
                        l.append(tempList);
                        tempList = [long]()
                        if (l.count == n){
                            return l;}
                    }
                }
            }
        }
    }
    func getDataToSend(_ n:Int)->[long] {
        var l = [long]()
        if (n == 0){
            return l;}
        var thisLoopSendTime = 0;
        while (true) {
            thisLoopSendTime+=1;
            if (thisLoopSendTime > maxAskTime){
                return l;}
            for i in peerPos ..< sendPos{
                if (!sendMap[i]!.got && sendMap[i]!.sendTime < thisLoopSendTime) {
                    sendMap[i]!.sendTime+=1;
                    l.append(i);
                    if (l.count == n){
                        return l;}
                }
            }
        }
    }
    
    func   addToDeadSock(_ channel:Int)
    {
        var num = sockMap[channel]!.num;
        var askList = sockMap[channel]!.askList;
        var dataList = sockMap[channel]!.dataList;
        for  i in askList {
            if (recvMap.keys.contains(i) && !recvMap[i]!.got){
                recvMap[i]!.sendTime-=1;}
        }
        if (dataList.count > 0) {
            var dd = dataList[0];
            if (sendMap.keys.contains(dd) && !sendMap[dd]!.got){
                sendMap[dd]!.sendTime-=1;}
        }
        sockMap[channel]?.sock.close()
        sockMap.removeValue(forKey: channel);
        staOut+=1;
        deadSock.append(num);
    }
    
    func dealTimeout() {
        var tt = getTime()
        var temp = Array(sockMap.keys)
        for  k in temp {
            if (sockMap[k]!.createTime + timeoutTime < tt){
                addToDeadSock(k);}
        }
    }
    
}
